package com.ccx.demo.config.init;

import com.google.common.base.Strings;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.utils.util.FPath;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 初始化单例类、实体类、接口需要的bean，因为单例类无法直接注入bean
 *
 * @author 谢长春
 */
@Component
@Slf4j
@RequiredArgsConstructor
@Order(-1) // 这里优先级一定要最高，否则其他的初始化使用到路径会报错
public class AppInit {
    private final AppConfig appConfig;
    private static ApplicationContext APP_CONTEXT;
//    /**
//     * 单线程服务
//     */
//    private static ExecutorService SINGLE_THREAD;
//    /**
//     * 多线程服务
//     */
//    private static ExecutorService MULTI_THREAD;
//    /**
//     * 缓存管理器
//     */
//    private static CacheManager CACHE_MANAGER;
//    /**
//     * QueryDSL 数据操作，通过此枚举获取到 jpa 查询对象，可以在接口中声明 default 方法后做更新删除查询操作
//     */
//    private static JPAQueryFactory JPA_QUERY_FACTORY;
//        sqlQueryFactory("QueryDSL 数据操作，通过此枚举获取到 sql 查询对象，可以在接口中声明 default 方法后做更新删除查询操作", SQLQueryFactory.class),
//        mongoTemplate("Mongodb 数据操作，通过此枚举获取到 mongo 查询对象，可以在接口中声明 default 方法后做更新删除查询操作", MongoTemplate.class),
    /**
     * 缓存从 spring application context 获取到的 bean， 避免每次通过反射获取
     */
    private static final Map<String, Object> BEAN_MAP = new ConcurrentHashMap<>();


    @Order(Ordered.HIGHEST_PRECEDENCE)
    @ConditionalOnExpression("'local'.equals('${spring.app.env}')")
    @Bean
    public CommandLineRunner printBeanCommandLineRunner(ApplicationContext context) {
        return args -> {
            System.out.println("打印所有bean:");
            Stream.of(context.getBeanDefinitionNames()).sorted().forEach(System.out::println);
        };
    }

    private static final String LOG_PATTERN = String.format(
            "\n┏ ┳%s {} %s ┳ ┓\n\n{}\n\n┗ ┻%s {} %s ┻ ┛"
            , Strings.repeat(" ┳", 30)
            , Strings.repeat(" ┳", 30)
            , Strings.repeat(" ┻", 30)
            , Strings.repeat(" ┻", 30)
    );

    @Order(Ordered.HIGHEST_PRECEDENCE)
    @Bean
    public CommandLineRunner fileInitCommandLineRunner() {
        return args -> {
            final AppConfig.Type type = appConfig.getFiles().getType();
            log.info(LOG_PATTERN,
                    "初始化路径配置",
                    Stream.of(AppConfig.Path.values())
                            .map(path -> {
                                if (Objects.equals(type, AppConfig.Type.DISK)) {
                                    return String.format("%s【%s】：%s", path.name(), path.comment, FPath.of(path.get()).mkdirs().absolute());
                                }
                                return String.format("%s【%s】：%s", path.name(), path.comment, path.get());
                            })
                            .collect(Collectors.joining("\n")),
                    "初始化路径配置"
            );
            log.info(LOG_PATTERN,
                    "文件访问路径配置",
                    Stream.of(AppConfig.URL.values())
                            .map(url -> String.format("%s【%s】：%s", url.name(), url.comment, url.value()))
                            .collect(Collectors.joining("\n")),
                    "文件访问路径配置"
            );
        };
    }

    @Bean
    public CommandLineRunner beanInitPrintCommandLineRunner(ApplicationContext context) {
        APP_CONTEXT = context;
        return args -> {
            log.info(LOG_PATTERN
                    , "环境配置"
                    , String.join("\n",
                            String.format("%s【%s】 : %s", "spring.application.name", "应用名称", appConfig.getName())
                            , String.format("%s【%s】 : %s", "spring.application.version", "当前运行版本号", appConfig.getVersion())
                            , String.format("%s【%s】 : %s", "spring.app.env", "当前环境：[local:本地开发环境|dev:远程开发环境|sit:系统测试|uat:验收测试|prod:生产]", appConfig.getEnv())
                            , String.format("%s【%s】 : %s", "spring.app.ip", "当前主机 IP 地址", appConfig.getIp())
                            , String.format("%s【%s】 : %s", "server.port", "当前服务端口", appConfig.getPort())
                    )
                    , "环境配置"
            );
        };
    }

    /**
     * 获取 Spring Context 对象
     *
     * @return ApplicationContext
     */
    public static ApplicationContext getAppContext() {
        return APP_CONTEXT;
    }

    public static ExecutorService getSingleThread() {
        return getBean("singleThread", ExecutorService.class);
    }

    public static ExecutorService getMultiThread() {
        return getBean("multiThread", ExecutorService.class);
    }

    public static CacheManager getCacheManager() {
        return getBean(CacheManager.class);
    }

    public static JPAQueryFactory getQueryFactory() {
        return getBean(JPAQueryFactory.class);
    }

    public static RedissonClient getRedissonClient() {
        return getBean(RedissonClient.class);
    }

    /**
     * 获取 bean， 并缓存到 map 集合，  避免每次通过反射获取
     */
    @SuppressWarnings({"unchecked"})
    public static <T> T getBean(Class<T> clazz) {
        if (!BEAN_MAP.containsKey(clazz.getName())) {
            // 缓存从 spring application context 获取到的 bean， 避免每次通过反射获取
            BEAN_MAP.put(clazz.getName(), APP_CONTEXT.getBean(clazz));
        }
        return (T) BEAN_MAP.get(clazz.getName());
    }

    /**
     * 获取 bean， 并缓存到 map 集合，  避免每次通过反射获取
     */
    @SuppressWarnings({"unchecked"})
    public static <T> T getBean(String beanName, Class<T> clazz) {
        if (BEAN_MAP.containsKey(beanName)) {
            // 缓存从 spring application context 获取到的 bean， 避免每次通过反射获取
            BEAN_MAP.put(beanName, APP_CONTEXT.getBean(beanName, clazz));
        }
        return (T) BEAN_MAP.get(clazz.getName());
    }
}
